package com.ibm.cps.demo;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import org.apache.log4j.Logger;

import com.ibm.interfaces.ITimeSeriesSchema;
import com.ibm.util.DataTypes;

public class PseudoDataProducer {
	private static final Logger logger = Logger
			.getLogger(PseudoDataProducer.class);
	private final String[] fieldNames;
	private final byte[] fieldTypes;
	private final String formatString;
	private final Map<String, NumberGenerator> fieldGenerators = new HashMap<>();

	public PseudoDataProducer(ITimeSeriesSchema schema,
			Map<String, NumberGenerator> generators) {
		this.fieldNames = schema.getTagNames();
		this.fieldTypes = schema.getDataTypes();
		this.formatString = creatFormatString();
		for (String field : fieldNames) {
			if (generators.containsKey(field)) {
				NumberGenerator presetGenerator = generators.get(field);
				fieldGenerators.put(field, presetGenerator);
			} else {
				fieldGenerators.put(field, new SlopeGenerator(1));
			}
		}
	}

	public PseudoDataProducer(ITimeSeriesSchema schema) {
		this.fieldNames = schema.getTagNames();
		this.fieldTypes = schema.getDataTypes();
		this.formatString = creatFormatString();
		for (String field : fieldNames) {
			fieldGenerators.put(field, new SlopeGenerator(1));
		}
	}

	private String creatFormatString() {
		StringBuffer result = new StringBuffer("{");
		for (int i = 0; i < fieldNames.length; i++) {
			String name = fieldNames[i];
			byte type = fieldTypes[i];
			String placeHolder = getPlaceHolderByDataType(type);
			if (i != 0) {
				result.append(",");
			}
			result.append("\"");
			result.append(name);
			result.append("\"");
			result.append(":");
			result.append(placeHolder);
		}
		result.append("}");
		return result.toString();
	}

	public byte[] produce(long timeseriesKey, long indicator) {
		Object[] valuesForTs = produceValueArray(timeseriesKey,
				System.currentTimeMillis(), fieldNames, fieldTypes, indicator);
		String messageForTs = String.format(formatString, valuesForTs);
		return messageForTs.getBytes();
	}

	private String getPlaceHolderByDataType(byte dataType) {
		switch (dataType) {
		case DataTypes.DT_BOOLEAN:
			return "%s";
		case DataTypes.DT_DOUBLE:
			return "%f";
		case DataTypes.DT_LONG:
			return "%d";
		case DataTypes.DT_STRING:
			return "%s";
		case DataTypes.DT_DOUBLE_ARRAY:
			return "%s";
		default:
			return "%d";
		}
	}

	private Object[] produceValueArray(long tsKey, long timestamp,
			String[] fieldNames, byte[] dataTypes, long indicator) {
		Object[] result = new Object[dataTypes.length];
		for (int i = 0; i < dataTypes.length; i++) {
			String name = fieldNames[i];
			if (name.equals("k")) {
				result[i] = new Long(tsKey);
			} else if (name.equals("t")) {
				result[i] = new Long(timestamp);
			} else {
				NumberGenerator generator = fieldGenerators.get(name);
				double seed = generator.generate(indicator);
				Object value = produceValue(dataTypes[i], seed);
				result[i] = value;
			}
		}
		return result;
	}

	private Object produceValue(byte dataType, double indicator) {
		switch (dataType) {
		case DataTypes.DT_BOOLEAN:
			return Boolean.valueOf(((long)Math.round(indicator)) % 2 == 0).toString();
		case DataTypes.DT_DOUBLE:
			return indicator * 1D;
		case DataTypes.DT_LONG:
			return ((long)Math.round(indicator));
		case DataTypes.DT_STRING:
			return "\"" + indicator + "\"";
		case DataTypes.DT_DOUBLE_ARRAY:
			return "[" + indicator * 1D + "," + indicator * 1D + ","
					+ indicator * 1D + "]";
		default:
			return indicator;
		}
	}

	public interface NumberGenerator {
		public double generate(double indicator);
	}

	public static class RandomGenerator implements NumberGenerator {
		final double mean;
		final double range;
		final Random r = new Random();

		public RandomGenerator(double mean, double range) {
			this.mean = mean;
			this.range = range;
		}

		@Override
		public double generate(double indicator) {
			return r.nextDouble() * range + mean - range / 2;
		}
	}

	public static class SinusoidalGenerator implements NumberGenerator {
		final double range;
		static final double step = 2 * Math.PI / 21;

		public SinusoidalGenerator(double range) {
			this.range = range;
		}

		@Override
		public double generate(double indicator) {
			return Math.sin(indicator * step) * range;
		}

	}

	public static class SlopeGenerator implements NumberGenerator {
		final double slope;

		public SlopeGenerator(double slope) {
			this.slope = slope;
		}

		@Override
		public double generate(double indicator) {
			return indicator * slope;
		}

	}
}
